﻿/// ****************************************************************************
/// Author:	        Robin Zhu
/// Create Date:	2012-4-28
/// Purpose: 		用于用户权限审核的静态类
/// ****************************************************************************
/// Modify By		Date			Remark
/// Robin           2012-9-27       调用VarAuthentication获得当前Identity
/// Robin           2014-2-24       使用MemoryCache提高效率
/// Robin           2014-3-11       使用统一的设置节
/// Robin           2015-2-26       增加以对象为参数的LogAction方法
/// Robin           2015-10-12      将VarAuthorization的Authorize(string appCode, string ticket, string path, string query)方法设为public
/// Robin           2015-10-12      修正Authorize(string appCode方法没有加载正确的Permissions的bug
/// ****************************************************************************
/// 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Web;
using System.Runtime.Caching;
using System.Text.RegularExpressions;
using RB.Utility;
using System.IO;

namespace RB.Web.Security
{
    public static class VarAuthorization
    {
        private static object _locker = new object();

        private const int CACHE_MINUTES = 20;

        private static IAuthorizeService _authorizeService = null;

        //private static MemoryCache _publicPathCache = new MemoryCache("PublicPathCache");
        //private static MemoryCache _authorizedPathCache = new MemoryCache("AuthorizedPathCache");

        private static Dictionary<string, Permission[]> _allPermissions = new Dictionary<string, Permission[]>();

        static VarAuthorization()
        {
            var config = SecurityConfigSection.Current;
            if (config != null)
            {
                Type serviceType = Type.GetType(config.AuthorizeServiceType);
                _authorizeService = (IAuthorizeService)Activator.CreateInstance(serviceType);
            }
        }

        public static string AppCode
        {
            get
            {
                var config = SecurityConfigSection.Current;
                if (config != null)
                    return config.AppCode;
                return "";
            }
        }

        public static bool Authorize(string path, string httpMethod)
        {

            VarIdentity ident = VarAuthentication.CurrentIdentity;
            if (ident != null)
            {
                string appCode = AppCode;
                //Guid userId = ident.UserId;

                path = UrlUtility.RemoveQuery(path);
                string query = UrlUtility.GetQuery(path);

                return Authorize(appCode, ident.Ticket, path, query);
            }

            return false;
        }

        internal static bool Authorize(HttpContext context)
        {
            try
            {
                if (VarAuthentication.IsLoginPage(context.Request.Url) ||
                    VarAuthentication.IsAccessDeniedPage(context.Request.Url))
                    return true;
            }
            catch
            {
            }
            //if (!context.Request.IsAuthenticated)
            //{
            //    throw new ArgumentException("用户未登录，不能进行权限审核");
            //}
            VarIdentity ident = null;
            if (context.Request.IsAuthenticated)
            {
                ident = context.User.Identity as VarIdentity;
            }
            string ticket = null;
            if (ident != null)
            {
                ticket = ident.Ticket;
                //throw new ArgumentException("请使用VarAuthenticationModule");
            }
            string appCode = AppCode;

            string path = context.Request.Url.AbsolutePath;
            string query = "";

            query = context.Request.ServerVariables["QUERY_STRING"];
            //if (context.Request.QueryString != null)
            //{
            //    query = context.Request.QueryString.ToString();
            //}
            try
            {
                if (context.Request.Form != null)
                {
                    query += "&" + context.Request.Form.ToString();
                }
            }
            catch (HttpRequestValidationException)
            {
                var request = context.Request;
                StreamReader reader = new StreamReader(request.InputStream, request.ContentEncoding);
                query += "&" + reader.ReadToEnd();
                request.InputStream.Position = 0;
            }

            return Authorize(appCode, ticket, path, query);
        }

        public static bool Authorize(string appCode, string ticket, string path, string query)
        {
            bool result = false;
            string permCode = null;

            lock (_locker)
            {
                var allpermissions = GetAllPermissions(appCode);
                Permission foundPerm = null;
                foreach (var permission in allpermissions)
                {
                    if (permission.IsMatch(path, query))
                    {
                        foundPerm = permission;
                        break;
                    }
                }
                if (foundPerm != null)
                {
                    if (foundPerm.IsPublic)
                    {
                        result = true;
                    }
                    else if (ticket != null)
                    {
                        var identity = VarAuthentication.CurrentIdentity;
                        if (identity.PermissionCodes.Contains(foundPerm.Code))
                        {
                            result = true;
                        }
                    }
                    permCode = foundPerm.Code;
                }
                else
                {
                    result = ticket != null;
                }

            }

            //ISet<string> pathSet = null;
            //if (ticket != null)
            //{
            //    pathSet = (ISet<string>)_authorizedPathCache[ticket];
            //    if (pathSet != null)
            //    {
            //        result = pathSet.Contains(url);
            //    }
            //    else
            //    {
            //        pathSet = new SortedSet<string>();
            //        _authorizedPathCache.Add(ticket, pathSet, DateTimeOffset.Now.AddMinutes(CACHE_MINUTES));
            //    }
            //}
            //else
            //{
            //    result = _publicPathCache.Contains(url);
            //}
            //if (!result)
            //{
            //    result = _authorizeService.Authorize(appCode, ticket, url, httpMethod);
            //    if (result)
            //    {
            //        if (pathSet != null)
            //        {
            //            pathSet.Add(url);
            //        }
            //        else
            //        {
            //            _publicPathCache.Add(url, url, DateTimeOffset.Now.AddMinutes(CACHE_MINUTES));
            //        }

            //    }
            //}

            LogAction(appCode, ticket, permCode, path, query, result);

            return result;
        }

        private static void LogAction(string appCode, string ticket, string permCode, string path, string query, bool result)
        {
            if (SecurityConfigSection.Current.LogActionLevel == 1 && result || 
                SecurityConfigSection.Current.LogActionLevel > 1)
            {
                HttpContext context = HttpContext.Current;

                string ipAddress = null;
                string httpForwarded = null;
                if (context != null && context.Request != null)
                {
                    ipAddress = context.Request.UserHostAddress;
                    httpForwarded = context.Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
                }
                try
                {
                    _authorizeService.LogAction(appCode, ticket, permCode, path + "?" + query, result, ipAddress, httpForwarded);
                }
                catch (InvalidOperationException)
                {

                }
            }
        }

        public static void LogAction(UserActionLog actionLog)
        {
            try
            {
                _authorizeService.LogAction(actionLog.AppCode, actionLog.Ticket, 
                    actionLog.PermCode, actionLog.PathAndQuery, 
                    !actionLog.Unauthorized, actionLog.IPAddress, actionLog.HttpForwarded);
            }
            catch (InvalidOperationException)
            {

            }
        }

        public static AppMenu[] GetAppMenus()
        {
            VarIdentity ident = VarAuthentication.CurrentIdentity;
            if (ident != null)
            {
                string appCode = AppCode;

                var result = _authorizeService.GetAppMenus(appCode, ident.UserId);

                if (result.Length > 0)
                {
                    Regex regEx = new Regex(@"\<[\w\-.:*]+\>");

                    foreach (var appMenu in result)
                    {
                        if (!string.IsNullOrEmpty(appMenu.Url))
                        {
                            regEx.Replace(appMenu.Url, (match) =>
                            {
                                var str = ConfigurationManager.AppSettings[match.Value];
                                return str ?? string.Empty;
                            });
                        }
                    }
                }

                return result;
            }

            return new AppMenu[0];
        }

        internal static Permission[] GetAllPermissions(string appCode)
        {
            Permission[] result = null;
            if (!_allPermissions.ContainsKey(appCode))
            {
                result = _authorizeService.GetAllPermissions(appCode);
                _allPermissions.Add(appCode, result);
            }
            else
            {
                result = _allPermissions[appCode];
            }
            return result;
        }

        internal static void RedirectToAccessDenied(HttpContext context)
        {
            var config = SecurityConfigSection.Current;
            if (string.IsNullOrWhiteSpace(config.AccessDeniedUrl))
            {
                VarAuthentication.RedirectToLogin(context);
            }
            else
            {
                string loginUrl = config.LoginUrl;
                String strUrl = null;
                if (loginUrl.StartsWith("/"))
                {
                    strUrl = context.Request.Path;
                }
                else
                {
                    strUrl = context.Request.Url.AbsoluteUri;
                }

                strUrl = UrlUtility.RemoveQuery(strUrl, "ticket");

                string strRedirect;
                if (loginUrl.IndexOf('?') >= 0)
                {
                    strRedirect = loginUrl + "&" + "ReturnUrl=" + HttpUtility.UrlEncode(strUrl, context.Request.ContentEncoding);
                }
                else
                {
                    strRedirect = loginUrl + "?" + "ReturnUrl=" + HttpUtility.UrlEncode(strUrl, context.Request.ContentEncoding);
                }
                strRedirect += "&appCode=" + VarAuthorization.AppCode;

                strRedirect = config.AccessDeniedUrl + "?loginUrl=" + HttpUtility.UrlEncode(strRedirect);

                context.Response.Redirect(strRedirect, false);
            }
        }

        public static string GetRoles(string url)
        {
            var roles = _authorizeService.GetRolesByUrl(SecurityConfigSection.Current.AppCode, url);
            return string.Join(",", roles);
        }

        public static AppEntry[] GetAppEntries()
        {
            VarIdentity ident = VarAuthentication.CurrentIdentity;
            if (ident != null)
            {
                return _authorizeService.GetAppEntries(ident.Ticket);
            }
            return new AppEntry[0];
        }
    }
}
